/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-会话管理-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.session.filters;

import com.taipingframework.boot.cache.redis.StandardCacheKeyBuilder;
import com.taipingframework.boot.session.HttpSessionManager;
import com.taipingframework.boot.session.constant.SessionCachePathEnum;
import com.taipingframework.boot.session.constant.SessionConstant;
import com.taipingframework.utility.constant.ApplicationConstant;
import com.taipingframework.utility.thread.threadlocal.TransmittableThreadLocalHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

/**
 * 主要实现两件事：
 * （1）记录发送HTTP请求的远程客户端设备标识【针对访客和白名单API】，允许 TransmittableThreadLocalHelper.getEnvironment().get("key") 的方式获取；
 * （2）刷新客户端IP与服务器端会话数据的绑定关系【要求登录】。
 */
@Slf4j
public class HttpSessionFilter extends OncePerRequestFilter {

    @Override
    protected void initFilterBean() throws ServletException {
        String[] stackTraceArray = Arrays.stream(Thread.currentThread().getStackTrace())
                .map(StackTraceElement::toString)
                .toArray(String[]::new);
        log.debug("HttpSessionFilter.init\r\n\r\tat {}", String.join("\r\n\r\tat ", stackTraceArray[1]));
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain chain) throws ServletException, IOException {
        // 获取发送HTTP请求的远程客户端设备标识
        String remoteClientId = TransmittableThreadLocalHelper.getEnvironment().get(ApplicationConstant.REMOTE_CLIENT_IP_KEY);

        HttpSession session = request.getSession(false);
        if (Objects.nonNull(session)) {
            String loginName = String.valueOf(session.getAttribute(SessionConstant.SESSION_LOGIN_KEY));
            // 1 记录当前会话对象映射的登录名【在spring-web会话管理模块DefaultSessionHandler.java中退出登录成功时获取】
            TransmittableThreadLocalHelper.getEnvironment().putIfAbsent(SessionConstant.SESSION_LOGIN_KEY, loginName);

            // 2 在ThreadLocal中存放Access-Token【在StandardCacheKeyBuilder.java中获取】
            // 我们约定：如果当前会话对象存在就能够保证在TransmittableThreadLocalHelper.getEnvironment()中保存的Access-Token一定存在！
            String authTokenKey = StandardCacheKeyBuilder.getAuthTokenKey();
            TransmittableThreadLocalHelper.getEnvironment().putIfAbsent(authTokenKey, session.getId());

            // 3 在ThreadLocal中存放当前HTTP请求的会话对象最大持续时长【在CommonCacheHandler.java中获取】
            TransmittableThreadLocalHelper.getEnvironment().putIfAbsent(ApplicationConstant.SESSION_DURATION_KEY, String.valueOf(session.getMaxInactiveInterval()));

            // 4 刷新客户端IP与服务器端会话数据的绑定关系【优化措施：使用线程池异步处理刷新会话的逻辑】
            log.debug("会话管理过滤器 - 远程客户端（{}）刷新会话缓存", remoteClientId);
            HttpSessionManager.refreshTerminalSession(loginName, remoteClientId, HttpSessionManager.readClientUserProfile(request));
            HttpSessionManager.removeExpiredTerminalSession(loginName, SessionCachePathEnum.SESSION_CREDENTIALS_PATH);
            HttpSessionManager.removeExpiredTerminalSession(remoteClientId, SessionCachePathEnum.SESSION_PROFILE_PATH);
        } else {
            // 5 nothing to do.
            log.debug("会话管理过滤器 - 远程客户端（{}）发起临时会话", remoteClientId);
        }

        chain.doFilter(request, response);

        log.debug("HttpSessionFilter.doFilterInternal - end");
    }

}
